/*
 * Copyright 2013 Luson Foundation and other contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.aprilis.luson;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.core.SimpleAnalyzer;
import org.apache.lucene.analysis.core.StopAnalyzer;
import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.aprilis.sys.Constants;

/**
 * @author rk
 * 
 */
public class LusonSearcher {

	public LusonSearcher(String indexDir) throws IOException {
		msIndexDir = indexDir;
		moIndexReader = null;
		moIndexSearcher = null;
		moAnalyzer = new WhitespaceAnalyzer(Version.LUCENE_43);
		msFieldName = null;
		msFieldValue = null;
		mlTimeTakenInMillis = 0;
	}

	public LusonSearcher(String indexDir, Constants.AnalyzerTypes eAnalyzerType) {
		msIndexDir = indexDir;
		moAnalyzer = null;

		switch (eAnalyzerType) {
			case STANDARD :
				moAnalyzer = new StandardAnalyzer(Version.LUCENE_43);
				break;
			case SIMPLE :
				moAnalyzer = new SimpleAnalyzer(Version.LUCENE_43);
				break;
			case STOP :
				moAnalyzer = new StopAnalyzer(Version.LUCENE_43);
				break;
			case WHITESPACE :
			default :
				moAnalyzer = new WhitespaceAnalyzer(Version.LUCENE_43);
				break;
		}

		moIndexReader = null;
		moIndexSearcher = null;
		msFieldName = null;
		msFieldValue = null;
		mlTimeTakenInMillis = 0;
	}

	public void openIndex(String sCollectionName) throws IOException {
		moIndexReader = DirectoryReader.open(FSDirectory.open(new File(msIndexDir + "//" + sCollectionName)));

		moIndexSearcher = new IndexSearcher(moIndexReader);
	}

	public List<String> search(String sCollectionName, String fldName, String fldValue, Constants.SearchTypes srchType) {
		try {

			if (fldName == null || fldValue == null)
				return null;

			msFieldName = fldName;
			msFieldValue = fldValue;

			if (moIndexSearcher == null)
				openIndex(sCollectionName);

			switch (srchType) {
				case QUERY_PARSER :
					return usingQueryParser();
				case TERM :
					return usinTermQuery();
				case WILDCARD :
					return usinWildCardQuery();
				case BOOLEAN :
					return usinBooleanQuery();
				case PREFIX :
					return usinPrefixQuery();
				case PHRASE :
					return usinPhraseQuery();
				case FUZZY :
					return usinFuzzyQuery();
				case MATCH_ALL :
					return matchAll();
				default:
					System.out.println("Invalid search type passed as argument.");
			}
		} catch (IOException e) {
			e.printStackTrace();

			return null;
		}

		return null;
	}

	public List<String> search(String sCollectionName, Query query) {
		moQuery = query;

		return execute();
	}

	private List<String> usingQueryParser() {

		try {
			QueryParser qparser = new QueryParser(Version.LUCENE_43, msFieldName, moAnalyzer);
			moQuery = qparser.parse(msFieldValue);

			System.out.println("usingQueryParser => query = " + moQuery);

			return execute();

		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	private List<String> usinTermQuery() {
		moQuery = new TermQuery(new Term(msFieldName, msFieldValue));
		System.out.println("usinTermQuery => query = " + moQuery);

		return execute();
	}

	private List<String> usinWildCardQuery() {
		moQuery = new WildcardQuery(new Term(msFieldName, msFieldValue));
		System.out.println("usinWildCardQuery => query = " + moQuery);

		return execute();
	}

	/**
	 * Tokenize the msFieldValue based on COMMA and each word is added to the
	 * query as a Term and searched for its mandatory presence.
	 * 
	 * @return
	 */
	private List<String> usinBooleanQuery() {

		moQuery = new BooleanQuery();
		for (String word : msFieldValue.split(Constants.COMMA)) {
			Query fldTermQuery = new TermQuery(new Term(msFieldName, word.trim()));
			((BooleanQuery) moQuery).add(fldTermQuery, BooleanClause.Occur.MUST); // mandatory
		}

		System.out.println("usinBooleanQuery => query = " + moQuery);

		return execute();
	}

	private List<String> usinPrefixQuery() {
		moQuery = new PrefixQuery(new Term(msFieldName, msFieldValue));
		System.out.println("usinPrefixQuery => query = " + moQuery);

		return execute();
	}

	/**
	 * PhraseQuery needs index to be analyzed using WhitespaceAnalyzer Tokenize
	 * the msFieldValue based on COMMA and each word is added to the query as a
	 * Term and searched within a Slop of DEFAULT_PHRASE_SLOP.
	 */
	private List<String> usinPhraseQuery() {
		PhraseQuery query = new PhraseQuery();
		query.setSlop(Constants.DEFAULT_PHRASE_SLOP);
		for (String word : msFieldValue.split(Constants.COMMA)) {
			query.add(new Term(msFieldName, word.trim()));
		}

		moQuery = query;
		System.out.println("usinPhraseQuery => query = " + moQuery);

		return execute();
	}

	private List<String> usinFuzzyQuery() {
		moQuery = new FuzzyQuery(new Term(msFieldName, msFieldValue));
		System.out.println("usinFuzzyQuery => query = " + moQuery);

		return execute();
	}

	private List<String> matchAll() {
		moQuery = new MatchAllDocsQuery();
		System.out.println("matchAll => query = " + moQuery);

		return execute();
	}

	private List<String> execute() {

		try {
			long startTime = System.currentTimeMillis();
			TopDocs searchHits = moIndexSearcher.search(moQuery, null, Constants.ROWS_LIMIT);
			mlTimeTakenInMillis = (System.currentTimeMillis() - startTime);

			List<String> matchedRecords = new ArrayList<String>();
			for (ScoreDoc scoreDoc : searchHits.scoreDocs) {
				Document doc = moIndexSearcher.doc(scoreDoc.doc);

				// System.out.println(msFieldName + " = "
				// + doc.get("record"));

				matchedRecords.add(doc.get("record"));
			}

			return matchedRecords;

		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	public void closeIndex() throws IOException {
		if (moIndexReader != null)
			moIndexReader.close();
	}

	public long getQueryTimeTaken() {
		return mlTimeTakenInMillis;
	}

	private IndexReader moIndexReader;

	private IndexSearcher moIndexSearcher;

	private Analyzer moAnalyzer;

	private Query moQuery;

	private long mlTimeTakenInMillis;

	private String msIndexDir;

	private String msFieldName;

	private String msFieldValue;
}
